﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Reflection;

namespace Aeon.Emulator.Decoding.Recompiler
{
    internal sealed class RegisterExpressions
    {
        private static Dictionary<string, int> WordSizes = new Dictionary<string, int>()
        {
            { "AL", 1 }, { "AH", 1 }, { "BL", 1 }, { "BH", 1 },
            { "CL", 1 }, { "CH", 1 }, { "DL", 1 }, { "DH", 1 },
            { "AX", 2 }, { "BX", 2 }, { "CX", 2 }, { "DX", 2 },
            { "SI", 2 }, { "DI", 2 }, { "BP", 2 }, { "SP", 2 },
            { "CS", 2 }, { "DS", 2 }, { "SS", 2 }, { "ES", 2 },
            { "FS", 2 }, { "GS", 2 }, { "EAX", 4 }, { "EBX", 4 },
            { "ECX", 4 }, { "EDX", 4 }, { "ESI", 4 }, { "EDI", 4 },
            { "EBP", 4 }, { "ESP", 4 }, { "EIP", 4 }
        };

        private Processor processor;

        public RegisterExpressions(Processor processor)
        {
            this.processor = processor;
        }

        public int GenerateValue(string register, ILGenerator il)
        {
            int size = GenerateReference(register, il);
            if(size == 1)
                il.Emit(OpCodes.Ldind_U1);
            else if(size == 2)
                il.Emit(OpCodes.Ldind_U2);
            else if(size == 4)
                il.Emit(OpCodes.Ldind_U4);
            else
                throw new ArgumentException();

            return size;
        }
        public int GenerateReference(string register, ILGenerator il)
        {
            IntPtr address;

            unsafe
            {
                void* ptr;

                switch(register)
                {
                case "EAX":
                case "AX":
                case "AL":
                    ptr = this.processor.PAX;
                    break;

                case "AH":
                    ptr = this.processor.PAH;
                    break;

                case "EBX":
                case "BX":
                case "BL":
                    ptr = this.processor.PBX;
                    break;

                case "BH":
                    ptr = this.processor.PBH;
                    break;

                case "ECX":
                case "CX":
                case "CL":
                    ptr = this.processor.PCX;
                    break;

                case "CH":
                    ptr = this.processor.PCH;
                    break;

                case "EDX":
                case "DX":
                case "DL":
                    ptr = this.processor.PDX;
                    break;

                case "DH":
                    ptr = this.processor.PDH;
                    break;

                case "ESI":
                case "SI":
                    ptr = this.processor.PSI;
                    break;

                case "EDI":
                case "DI":
                    ptr = this.processor.PDI;
                    break;

                case "EBP":
                case "BP":
                    ptr = this.processor.PBP;
                    break;

                case "ESP":
                case "SP":
                    ptr = this.processor.PSP;
                    break;

                case "EIP":
                    ptr = this.processor.PIP;
                    break;

                case "CS":
                    ptr = this.processor.PCS;
                    break;

                case "DS":
                    ptr = this.processor.PDS;
                    break;

                case "SS":
                    ptr = this.processor.PSS;
                    break;

                case "ES":
                    ptr = this.processor.PES;
                    break;

                case "FS":
                    ptr = this.processor.PFS;
                    break;

                case "GS":
                    ptr = this.processor.PGS;
                    break;

                default:
                    throw new ArgumentException();
                }

                address = new IntPtr(ptr);
            }

            if(IntPtr.Size == 4)
                il.Emit(OpCodes.Ldc_I4, address.ToInt32());
            else if(IntPtr.Size == 8)
                il.Emit(OpCodes.Ldc_I8, address.ToInt64());
            else
                throw new NotSupportedException();

            il.Emit(OpCodes.Conv_I);

            return WordSizes[register];
        }
        public void GenerateSegmentBaseValue(string register, ILGenerator il)
        {
            IntPtr address;

            unsafe
            {
                void* ptr;

                switch(register)
                {
                case "CS":
                    ptr = &this.processor.segmentBases[(int)SegmentIndex.CS];
                    break;

                case "DS":
                    ptr = &this.processor.segmentBases[(int)SegmentIndex.DS];
                    break;

                case "SS":
                    ptr = &this.processor.segmentBases[(int)SegmentIndex.SS];
                    break;

                case "ES":
                    ptr = &this.processor.segmentBases[(int)SegmentIndex.ES];
                    break;

                case "FS":
                    ptr = &this.processor.segmentBases[(int)SegmentIndex.FS];
                    break;

                case "GS":
                    ptr = &this.processor.segmentBases[(int)SegmentIndex.GS];
                    break;

                default:
                    throw new ArgumentException();
                }

                address = new IntPtr(ptr);
            }

            if(IntPtr.Size == 4)
                il.Emit(OpCodes.Ldc_I4, address.ToInt32());
            else if(IntPtr.Size == 8)
                il.Emit(OpCodes.Ldc_I8, address.ToInt64());
            else
                throw new NotSupportedException();

            il.Emit(OpCodes.Conv_I);
            il.Emit(OpCodes.Ldind_U4);
        }
        public void GenerateSegmentBaseValue(string register, SegmentRegister overrideSegment, ILGenerator il)
        {
            if(overrideSegment == SegmentRegister.Default)
                GenerateSegmentBaseValue(register, il);
            else
                GenerateSegmentBaseValue(overrideSegment.ToString(), il);
        }
    }
}
